<!--
  Copyright 2018 Google LLC

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html">
<link rel="import" href="../../bower_components/iron-icons/iron-icons.html">
<link rel="import" href="../../bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../../bower_components/paper-button/paper-button.html">
<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="../../bower_components/paper-spinner/paper-spinner.html">
<link rel="import" href="../../bower_components/paper-styles/color.html">
<link rel="import" href="../common/if-else/if-else.html">
<link rel="import" href="../common/paginated-list/paginated-list.html">
<link rel="import" href="../common/render-either/render-either.html">
<link rel="import" href="../common/impact-button/impact-button.html">
<link rel="import" href="search-control-panel.html">

<dom-module id="testcase-list">
  <link rel="import" href="../../stylesheets/main.css" type="css">
  <link rel="import" href="../technology/technology.css" type="css">
  <script src="../../javascripts/error.js" type="text/javascript"></script>
  <template>
    <style>
      :host {
        display: block;
      }

      :host .error {
        text-align: left;
        color: #ca4545;
        display: block;
        padding: 30px 60px;
        box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.2);
        background-color: #f2f7fa;
      }

      :host .error .title {
        display: block;
        font-weight: bold;
      }

      :host .error .trace-dump {
        display: block;
        overflow: auto;
        font-family: 'Source Code Pro', monospace;
        white-space: pre;
        margin-top: 10px;
        font-size: 12px;
      }

      :host .empty {
        text-align: center;
        color: #777;
        display: block;
        padding: 30px;
        font-weight: bold;
        font-size: 18px;
        box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.2);
        background-color: #f2f7fa;
      }

      :host .empty .keyword-tip {
        display: block;
        margin-top: 22px;
        color: #888;
        font-size: 14px;
        font-weight: normal;
        line-height: 22px;
      }

      :host .testcases {
        display: block;
        overflow: hidden;
        box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.2);
        background-color: #f2f7fa;
      }

      :host .testcases > div {
        display: block;
        margin: 0px;
        padding: 5px 0px;
      }

      :host .testcases > div.closed {
        background-color: #e7f7f5;
      }

      :host .testcases > div.row:first-child {
        border-top: 0px solid #ccc;
      }

      :host .testcases > div.row {
        border-top: 1px solid #ccc;
      }

      :host .too-many-items-tip {
        display: block;
        color: #888;
        text-align: center;
        font-size: 14px;
        font-weight: normal;
        line-height: 22px;
        margin: 20px 0px;
      }

      :host .testcases > div.row > table {
        width: 100%;
      }

      :host .testcases > div.row > table td {
        vertical-align: top;
      }

      :host .testcases > div.row > table tr > td {
        text-align: left;
        line-height: 22px;
        padding-left: 5px;
      }

      :host .testcases > div.row > table tr > td.main {
        font-weight: bold;
        font-size: 16px;
      }

      :host .testcases > div.row > table tr > td.labels {
        text-align: right;
      }

      :host .testcases > div.row a {
        color: #333;
        text-decoration: none;
      }

      :host .testcases > div.row a:visited {
        color: #551A8B !important;
        text-decoration: none;
      }

      :host .testcases > div.row a:hover {
        color: #4078c0 !important;
      }

      :host .testcases > div.row .states {
        font-weight: normal;
        font-family: 'Source Code Pro', monospace;
        text-decoration: none;
        white-space: nowrap;
        overflow: hidden;
        display: inline-block;
        line-height: 22px;
        font-size: 13px;
      }

      :host .testcases > div.row .states span {
        display: block;
      }

      :host .testcases > div.row span.time {
        display: inline-block;
        font-size: 12px;
        color: #666;
        font-weight: normal;
        margin-left: 5px;
      }

      :host .testcases > div.row a.group {
        display: inline-block;
        color: #888;
        margin-left: 5px;
      }

      :host .testcases > div.row > table tr > td.main > small > a {
        color: #888;
        text-decoration: none;
      }

      :host .testcases > div.row > table tr > td.main > small > a:hover {
        color: #4078c0;
      }


      :host .testcases > div.row .label {
        font-size: 13px;
        display: inline-block;
        margin-right: 5px;
        border: 1px solid #ccc;
        color: #ccc;
        padding: 2px 4px;
        vertical-align: middle;
        font-weight: normal;
        line-height: 12px;
        text-decoration: none;
      }

      :host .testcases > div.row span.fixed {
        border: 1px solid #1D8301;
        color: #1D8301;
      }

      :host .testcases > div.row .issue {
        border: 1px solid #01579B;
        color: #01579B;
      }

      :host .testcases > div.row span.project {
        border: 1px solid #5D4037;
        color: #5D4037;
      }

      :host .testcases > div.row span.platform {
        border: 1px solid #C51162;
        color: #C51162;
      }

      :host .testcases > div.row .impact {
        border: 1px solid #CB008E !important;
        color: #CB008E !important;
        --impact-button-color: #CB008E;
        cursor: pointer;
      }

      :host .testcases > div.row span.reproducible {
        border: 1px solid #5319E7;
        color: #5319E7;
      }

      :host .testcases > div.row span.security {
        border: 1px solid #FC2929;
        color: #FC2929;
      }

      :host .clickable {
        cursor: pointer;
      }

      :host .submit-button {
        position: absolute;
        top: 13px;
        right: 10px;
      }
    </style>
    <iron-ajax
      id="ajax"
      url="/testcases/load"
      method="POST"
      content-type="application/json"
      body="[[ajaxParams]]"
      loading="{{loading}}"
      last-request="{{request}}"
      last-error="{{error}}"
      last-response="{{response}}"
      on-error="handleError"
      on-request="handleRequest"
      on-response="handleResponse"
      debounce-duration="500"></iron-ajax>
    <app-toolbar>
      <paper-icon-button icon="menu" onclick="document.getElementById('drawer').toggle()"></paper-icon-button>
      <div id="title">Testcases</div>
      <template is="dom-if" if="[[!loading]]">
        <paper-icon-button class="submit-button" icon="refresh" on-tap="searchButtonTapped"></paper-icon-button>
      </template>
      <template is="dom-if" if="[[loading]]">
        <paper-spinner active="[[loading]]"></paper-spinner>
      </template>
    </app-toolbar>
    <div id="mainContent">
      <search-control-panel
          id="searchControlPanel"
          field-values="[[fieldValues]]"
          params="{{params}}"
          loading="[[loading]]"
          on-search-button-tapped="searchButtonTapped"></search-control-panel>
      <paginated-list
          id="paginatedList"
          result="{{result}}"
          params="{{params}}"
          keys="[[paramKeys]]"
          loading="[[loading]]"
          on-params-modified="paramsModified">
        <div class="error" hidden$="[[!shouldShowError(result, result.error)]]">
          <span class="title">[[result.error.message]]</span>
          <span class="trace-dump" hidden$="[[!result.error.traceDump]]"
            >[[result.error.traceDump]]</span>
        </div>
        <div class="empty" hidden$="[[!shouldShowEmpty(result, result.items)]]">
          Didn't find anything with the current search.<br/>
          <span class="keyword-tip">
            Searching matches only whole words, including words within camel-cased text.</br>
            For example, <strong>debug</strong>, <strong>break</strong>, <strong>tree</strong>, <strong>debugbreak</strong>, or <strong>break::tree</strong> match <strong>DebugBreak::Tree</strong>.</br>
            But <strong>de</strong>, <strong>ub</strong>, or <strong>ak::tr</strong> don't match it.
          </span>
        </div>
        <div class="testcases" hidden$="[[!shouldShowItems(result, result.items)]]">
          <template is="dom-repeat" items="[[result.items]]">
            <div class$="row {{closedClass(item)}}">
              <table>
                <tr>
                  <td class="main">
                    <a href="/testcase-detail/[[item.id]]">
                      [[item.crashType]]
                      <span class="time" title="Creation time">[[formatTime(item.timestamp)]]</span>
                    </a>
                    <template is="dom-if" if="[[item.groupId]]">
                      <a class="group" title="Click to view all similar testcases in this group." href="/testcases?q=group%3A[[item.groupId]]">
                        <iron-icon icon="view-module"></iron-icon>
                      </a>
                    </template>
                  </td>
                  <td class="labels">
                    <template is="dom-if" if="[[item.isClosed]]">
                      <span class="label fixed" title="[[formatFixedRange(item)]]">Fixed</span>
                    </template>
                    <template is="dom-if" if="[[shouldShowProject(fieldValues, item)]]">
                      <span
                          on-tap="filterProject"
                          data-project$="[[item.projectName]]"
                          class="label project clickable"
                          title="The crash is associated with the [[item.projectName]] project.">
                        Project [[item.projectName]]
                      </span>
                    </template>
                    <template is="dom-if" if="[[item.platform]]">
                      <span
                          on-tap="filterPlatform"
                          data-platform$="[[item.platform]]"
                          class="label platform clickable"
                          title="The crash was found on [[item.platform]].">
                        Platform [[item.platform]]
                      </span>
                    </template>
                    <if-else condition="[[item.isReproducible]]">
                      <span
                          on-tap="filterReproducible"
                          data-reproducible="yes"
                          slot="t"
                          class="label reproducible clickable"
                          title="[[formatRegressionRange(item)]]">Reliably reproduces</span>
                      <span
                          on-tap="filterReproducible"
                          data-reproducible="no"
                          slot="f"
                          class="label clickable"
                          title="The crash is flaky (not reproducible).">Reliably reproduces</span>
                    </if-else>
                    <if-else condition="[[item.isSecurity]]">
                      <span
                          on-tap="filterSecurity"
                          data-security="yes"
                          slot="t"
                          class="label security clickable"
                          title="The crash is security-relevant.">Security</span>
                      <span
                          on-tap="filterSecurity"
                          data-security="no"
                          slot="f"
                          class="label clickable"
                          title="The crash is NOT security-relevant.">Security</span>
                    </if-else>
                  </td>
                </tr>
                <tr>
                  <td>
                    <a href="/testcase-detail/[[item.id]]">
                      <span class="states">
                        <template is="dom-repeat" items="[[item.crashStateLines]]" as="state">
                          <span>[[state]]</span>
                        </template>
                      </span>
                    </a>
                  </td>
                  <td class="labels">
                    <template is="dom-if" if="[[item.issueId]]">
                      <a class="issue label" href="/issue/[[item.id]]/[[item.issueId]]" title="Click to view the associated issue [[item.issueId]] in the issue tracker.">Issue [[item.issueId]]</a>
                    </template>
                    <template is="dom-if" if="[[item.showImpacts]]">
                      <if-else condition="[[item.isImpactSet]]">
                        <if-else slot="t" condition="[[item.impactsProduction]]">
                          <span slot="t">
                            <template is="dom-if" if="[[item.impacts.stable]]">
                              <impact-button
                                  title="The crash impacts the stable build."
                                  class="label impact"
                                  release="Stable"
                                  version="[[item.impacts.stable]]"
                                  on-tap="impactButtonTapped"></impact-button>
                            </template>
                            <template is="dom-if" if="[[item.impacts.beta]]">
                              <impact-button
                                  title="The crash impacts the beta build."
                                  class="label impact"
                                  release="Beta"
                                  version="[[item.impacts.beta]]"
                                  on-tap="impactButtonTapped"></impact-button>
                            </template>
                          </span>
                          <span slot="f" class="label impact" title="The crash impacs Head." on-tap="filterImpact" data-impact="head">Head</span>
                        </if-else>
                        <span slot="f" class="label" title="The crash's impacted versions are not computed yet.">Impact is pending</span>
                      </if-else>
                    </template>
                  </td>
                </tr>
              </table>
            </div>
          </template>
        </div>
      </paginated-list>
      <div class="too-many-items-tip" hidden$="[[!shouldShowFilterTip(result)]]">
        Too many items? Additional filters on issue, platform, impact, stable version, and beta version are supported. <br/>
        Try adding <strong>issue:667865</strong>, <strong>platform:windows</strong>, or <strong>stable:54</strong> to the search textbox.
      </div>
    </div>
  </template>
  <script>
    class TestcaseList extends Polymer.Element {
      static get is() { return 'testcase-list'; }

      static get properties() {
        return {
          result:  Object,
          fieldValues:  Object,
          params: Object,
          paramKeys: {
            type: Array,
            value: [
              'page', 'q', 'project', 'issue', 'reproducible', 'security', 'open',
              'fuzzer', 'job', 'impact', 'showall'
            ]
          },
          response: Object,
          loading: {
            type: Boolean,
            value: false
          },
        };
      }

      shouldShowError(result, error) {
        return !!error;
      }

      shouldShowEmpty(result, items) {
        return items && items.length == 0;
      }

      shouldShowItems(result, items) {
        return items && items.length > 0;
      }

      ready() {
        super.ready();

        if (this.result) {
          this.set('result.error', this.result.error || null);
        }

        this.paramKeys.forEach((key) => {
          if (!this.params[key]) {
            this.set(`params.${key}`, '');
          }
        });

        this.$.paginatedList.init();
      }

      searchButtonTapped(ev) {
        this.params.page = '';
        this.set('result.page', 1);
        this.search();
      }

      paramsModified(ev) {
        this.search();
      }

      search() {
        this.ajaxParams = {};
        let hasParam = false;
        this.paramKeys.forEach((key) => {
          if (this.params[key]) {
            hasParam = key != 'page' && true;
            this.ajaxParams[key] = this.params[key];
          }
        });

        if (!hasParam) {
          this.ajaxParams.showall = '1';
          this.set('params.showall', '1');
        }

        this.$.paginatedList.updateQuery();
        this.$.ajax.generateRequest();
      }

      handleRequest() {
        var reqs = this.$.ajax.activeRequests;
        for (let i=0;i<reqs.length;i++) {
          if (reqs[i] != this.request) {
            reqs[i].abort();
          }
        }
      }

      handleResponse() {
        this.result = this.response;
        this.set('result.error', null);
        this.$.paginatedList.save();
        this.$.searchControlPanel.focus();
      }

      handleError() {
        let error = parseError(this.error);
        if (!error) {
          // this.error is undefined because its request is aborted.
          return;
        }

        this.set('result.items', null);
        this.set('result.error', error);

        this.$.paginatedList.save();
        this.$.searchControlPanel.focus();
      }

      formatFixedRange(item) {
        if (!item.fixedRange) {
          return 'The flaky crash is no longer seen, so considered fixed.';
        } else if (item.fixedRange == 'Yes') {
          return 'The crash is fixed in the latest custom binary.';
        } else {
          return 'The crash is fixed in the revision range: ' + item.fixedRange;
        }
      }

      formatRegressionRange(item) {
        if (item.regressionRange) {
          return 'The crash is reliably reproducible and found in the revision range: ' +
              item.regressionRange;
        } else {
          return 'The crash is reliably reproducible.'
        }
      }

      formatTime(secondsFromEpoch) {
        let date = new Date(secondsFromEpoch * 1000);
        return date.toLocaleDateString(
            'en-US',
            {weekday:'short', year:'numeric', month:'short', day:'numeric',
             hour:'numeric', minute:'numeric'});
      }

      closedClass(item) {
        if (item.isClosed) {
          return 'closed';
        } else {
          return '';
        }
      }

      shouldShowFilterTip(result) {
        return result && result.totalPages >= 3;
      }

      shouldShowProject(fieldValues, item) {
        return (
            fieldValues && fieldValues.projects &&
            fieldValues.projects.length > 1 && item.projectName);
      }

      filterProject(ev) {
        this.set('params.project', ev.target.dataset.project);
        this.search();
      }

      filterPlatform(ev) {
        this.set(
            'params.q',
            `${(this.params.q || '')} platform:${ev.target.dataset.platform}`.trim());
        this.search();
      }

      filterReproducible(ev) {
        this.set('params.reproducible', ev.target.dataset.reproducible);
        this.search();
      }

      filterSecurity(ev) {
        this.set('params.security', ev.target.dataset.security);
        this.search();
      }

      filterImpact(ev) {
        this.set('params.impact', ev.target.dataset.impact);
        this.set('params.q', this.clearStableBetaFilter(this.params.q));
        this.search();
      }

      clearStableBetaFilter(q) {
        return (q || '').replace(
            new RegExp(`(^| )(stable|beta):[^ ]+`, 'gi'), '');
      }

      impactButtonTapped(ev) {
        ev.preventDefault();
        ev.stopPropagation();

        if (!ev.detail.release) { return; }


        let q = this.clearStableBetaFilter(this.params.q);
        if (ev.detail.version) {
          this.set('params.impact', '');
          q = `${q} ${ev.detail.release}:${ev.detail.version}`.trim();
        } else {
          this.set('params.impact', ev.detail.release);
        }

        this.set('params.q', q);
        this.search();
      }
    }

    customElements.define(TestcaseList.is, TestcaseList);
  </script>
</dom-module>

